﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using MongoDB.Bson;
using MongoDB.Driver;
using WHLRDF.ORM;
using System.Linq;
using Newtonsoft.Json;

namespace WHLRDF.Cache
{
    public class MongoDBService : BaseCacheService, ICacheService
    {
        private readonly static object locked = new object();
        private static MongoDBService _instance;
        public static MongoDBService Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (locked)
                    {
                        if (_instance == null)
                        {
                            _instance = new MongoDBService();
                        }
                    }
                }

                return _instance;
            }
        }
        private MongoClient _client = null;
        private IMongoDatabase _database;
        private IMongoCollection<BsonDocument> _tCollection;
        /// <summary>
        /// 过期时间 常量
        /// </summary>
        public const string EXPRIETIME = "ExpireTime";
        /// <summary>
        /// key 常量值
        /// </summary>
        public const string KEYNAME = "Key";

        /// <summary>
        /// value 常量值
        /// </summary>
        public const string VALUENAME = "Value";

        public const string SYSTEMDBTABLESESSION= "DbTableSession";

        public const string SYSTEMDBTABLENAME= "SystemDbTable";
        public MongoDBService()
        {
            if (string.IsNullOrWhiteSpace(ApplicationEnvironments.Site.MongoConnection))
            {
                throw new Exception("连接字符串为空");
            }
            var mongoUrl = new MongoUrlBuilder(ApplicationEnvironments.Site.MongoConnection);
            // 创建并实例化客户端
            _client = new MongoClient(mongoUrl.ToMongoUrl());
            // 根据集合名称获取集合
            _database = _client.GetDatabase(mongoUrl.DatabaseName);
            _tCollection= _database.GetCollection<BsonDocument>(SYSTEMDBTABLENAME);
        }
        public bool Add(string key, object value)
        {
            this.Add(key,value,TimeSpan.FromMinutes(ApplicationEnvironments.Site.SessionTimeout),true);
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiresSliding"></param>
        /// <param name="expiressAbsoulte"></param>
        /// <returns></returns>
        public bool Add(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
        {
            this.Add(key, value, expiresSliding, true);
            return true;
        }

        public bool Add(string key, object value, TimeSpan expiresIn, bool isSliding = false)
        {
            IsArgumentNull(key);
            var document = _tCollection.Find(Builders<BsonDocument>.Filter.Eq(KEYNAME, key)).ToList();
            if (document == null || document.Count <= 0)
            {
                _tCollection.InsertOne(new BsonDocument{
                 { "_id",ObjectId.GenerateNewId()},
                { KEYNAME,key},
                { VALUENAME,value.ToJson()},
                { EntityBase.__CreateDate,DateTime.Now},
                { EXPRIETIME,DateTime.Now.AddMinutes(expiresIn.TotalMinutes)}
            });
            }
            else
            {
                var update = Builders<BsonDocument>.Update.Set(VALUENAME, value.ToJson());
                update = update.Set(EXPRIETIME, DateTime.Now.AddMinutes(expiresIn.TotalMinutes));
                _tCollection.UpdateOne(Builders<BsonDocument>.Filter.Eq("_id", document[0]["_id"]), update);
            }
            return true;
        }

        public Task<bool> AddAsync(string key, object value)
        {
            return AddAsync(key,value, TimeSpan.FromMinutes(ApplicationEnvironments.Site.SessionTimeout), true);
        }

        public Task<bool> AddAsync(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
        {
            return AddAsync(key, value, TimeSpan.FromMinutes(ApplicationEnvironments.Site.SessionTimeout), true);
        }

        public Task<bool> AddAsync(string key, object value, TimeSpan expiresIn, bool isSliding = false)
        {
            return  Task.Run<bool>(() =>
            {
                var document = _tCollection.Find(Builders<BsonDocument>.Filter.Eq(KEYNAME, key)).ToList();
                if (document == null || document.Count <= 0)
                {
                    _tCollection.InsertOneAsync(new BsonDocument{
                 { "_id",ObjectId.GenerateNewId()},
                { KEYNAME,key},
                { VALUENAME,value.ToJson()},
                { EntityBase.__CreateDate,DateTime.Now},
                { EXPRIETIME,DateTime.Now.AddMinutes(expiresIn.TotalMinutes)}
                });
                }
                else
                {
                    var update = Builders<BsonDocument>.Update.Set(VALUENAME, value.ToJson());
                    update = update.Set(EXPRIETIME, DateTime.Now.AddMinutes(expiresIn.TotalMinutes));
                    _tCollection.UpdateOneAsync(Builders<BsonDocument>.Filter.Eq("_id", document[0]["_id"]), update);
                }
                return true;
            });
        }

        public bool Clear()
        {
            return _tCollection.DeleteMany(Builders<BsonDocument>.Filter.Eq("1", "1")).DeletedCount>0;
             
        }

        public bool Exists(string key)
        {
            var document = _tCollection.Find(Builders<BsonDocument>.Filter.Eq(KEYNAME, key)).ToList();
            if (document == null || document.Count <= 0)
            {
                return false;
            }
            return true;
        }
        public Task<bool> ExistsAsync(string key)
        {
            return Task.Run<bool>(() => {
                var document = _tCollection.FindAsync(Builders<BsonDocument>.Filter.Eq(KEYNAME, key)).Result.ToList();
                if (document == null || document.Count <= 0)
                {
                    return false;
                }
                return true;
            });
         
        }

        public byte[] Get(string key)
        {
         
            var document= _tCollection.Find(Builders<BsonDocument>.Filter.Eq(KEYNAME, key)).FirstOrDefault();
            if (document != null )
            {
                return JSONHelper.FromJson<byte[]>(document[key].ToString());
            }
            return null;
        }

        public IDictionary<string, object> GetAll(List<string> keys)
        {
            var dict = new Dictionary<string, object>();

            keys.ToList().ForEach(item => dict.Add(item, GetCache(item)));

            return dict;
        }

        public Task<IDictionary<string, object>> GetAllAsync(List<string> keys)
        {
            throw new NotImplementedException();
        }

        public Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken))
        {
            IsArgumentNull(key);
            return Task.Run<byte[]>(() =>
            {
                var document = _tCollection.Find(Builders<BsonDocument>.Filter.Eq(KEYNAME, key)).FirstOrDefault();
                if (document != null)
                {
                    return JSONHelper.FromJson<byte[]>(document[key].ToString());
                }
                return null;
            });
        }

        public T GetCache<T>(string key) where T : class, new()
        {
            IsArgumentNull(key);
            var document = _tCollection.Find(Builders<BsonDocument>.Filter.Eq(KEYNAME, key)).FirstOrDefault();
            if (document != null)
            {
                return JSONHelper.FromJson<T>(document[key].ToString());
            }
            return default(T);
            
        }

        public object GetCache(string key)
        {
            IsArgumentNull(key);
            var document = _tCollection.Find(Builders<BsonDocument>.Filter.Eq(KEYNAME, key)).FirstOrDefault();
            if (document != null)
            {
                return JsonConvert.DeserializeObject(document[key].ToString());
            }
            return null;
        }

        public Task<T> GetCacheAsync<T>(string key) where T : class, new()
        {
            IsArgumentNull(key);
            return Task.Run<T>(() =>
            {
                var document = _tCollection.Find(Builders<BsonDocument>.Filter.Eq(KEYNAME, key)).FirstOrDefault();
                if (document != null)
                {
                    return JSONHelper.FromJson<T>(document[key].ToString());
                }
                return default(T);
            });
        }

        public Task<T> GetCacheAsync<T>(string key, Func<T> acquire) where T : class, new()
        {
            return Task.Run<T>(() =>
            {
                return this.GetOrCreate<T>(key, acquire);
            });
        }

        public Task<object> GetCacheAsync(string key)
        {
            return Task.Run<object>(() => { return this.GetCache(key); });
        }

        public T GetOrCreate<T>(string key, Func<T> acquire) where T : class, new()
        {
            return this.GetOrCreate<T>(key,acquire,false);
        }

        public T GetOrCreate<T>(string key, Func<T> acquire, bool isSliding) where T : class, new()
        {
            IsArgumentNull(key);
            if (this.Exists(key))
            {
                var value = this.GetCache<T>(key);
                if (value != null)
                {
                    return value;
                }
            }
            var result = acquire();
            if (result == null)
            {
                return result;
            }
            this.Add(key, result, TimeSpan.FromMinutes(ApplicationEnvironments.Site.SessionTimeout), isSliding);
            return result;
        }

        public T HGet<T>(string tableName, string key) where T : class, new()
        {
            IsDbArgumentNull(tableName, "", key);
            var tbCollection = _database.GetCollection<T>(tableName);
            DataProxy dataProxy = new DataProxy(typeof(T));
            return tbCollection.Find(Builders<T>.Filter.Eq(dataProxy.PrimaryKeyName, key)).FirstOrDefault();
        }

        public Task<T> HGetAsync<T>(string tableName, string key) where T : class, new()
        {
            IsDbArgumentNull(tableName, "", key);
            return Task.Run<T>(()=>{
              
                return HGet<T>(tableName,key);
            });
        }

        public T HGetOrCreate<T>(string tableName, string key, Func<T> acquire) where T : class, new()
        {
            IsDbArgumentNull(tableName,"",key);
            if (this.Exists(key))
            {
                var value = this.HGet<T>(tableName,key);
                if (value != null)
                {
                    return value;
                }
            }
            var result = acquire();
            if (result == null)
            {
                return result;
            }
            var tbCollection = _database.GetCollection<T>(tableName);
            tbCollection.InsertOne(result);
            return result;
        }

        public Task<T> HGetOrCreateAsync<T>(string tableName, string key, Func<T> acquire) where T : class, new()
        {
            IsDbArgumentNull(tableName, "", key);
            return Task.Run<T>(() => {
                return  HGetOrCreate<T>(tableName,key, acquire);
            });
        }

        public bool HRemove<T>(string tableName, params string[] keys)
        {
            IsDbArgumentNull(tableName, "", string.Join(',', keys));
            var tbCollection = _database.GetCollection<T>(tableName);
            DataProxy dataProxy = new DataProxy(typeof(T));
            return tbCollection.DeleteManyAsync(Builders<T>.Filter.AnyIn(dataProxy.PrimaryKeyName, keys)).Result.DeletedCount>0;
            
        }

        public bool HRemoveAsync<T>(string tableName, params string[] keys) 
        {
            IsDbArgumentNull(tableName, "", string.Join(',', keys));
            var tbCollection = _database.GetCollection<T>(tableName);
            DataProxy dataProxy = new DataProxy(typeof(T));
            tbCollection.DeleteManyAsync(Builders<T>.Filter.AnyIn(dataProxy.PrimaryKeyName, keys));
            return true;
        }

        public void HSet<T>(string tableName, string key, T value) where T : class, new()
        {
            var tbCollection = _database.GetCollection<T>(tableName);
            tbCollection.InsertOne(value);
        }

        public void HSetAsync<T>(string tableName, string key, T value) where T : class, new()
        {
            var tbCollection = _database.GetCollection<T>(tableName);
            tbCollection.InsertOneAsync(value);
        }

        public void Refresh(string key)
        {
           
        }

        public Task RefreshAsync(string key, CancellationToken token = default(CancellationToken))
        {
            throw new NotImplementedException();
        }

        public void Remove(string key)
        {
            throw new NotImplementedException();
        }

        public void RemoveAll(List<string> keys)
        {
            throw new NotImplementedException();
        }

        public Task RemoveAllAsync(List<string> keys)
        {
            throw new NotImplementedException();
        }

        public Task RemoveAsync(string key, CancellationToken token = default(CancellationToken))
        {
            throw new NotImplementedException();
        }

        public bool RemoveCache(string key)
        {
            throw new NotImplementedException();
        }

        public Task<bool> RemoveCacheAsync(string key)
        {
            throw new NotImplementedException();
        }

        public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
        {
            throw new NotImplementedException();
        }

        public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken))
        {
            throw new NotImplementedException();
        }
    }
}
